⚡ OpenAI Triton
从介绍到模型实战(完整版)
起于 CUDA,不止于 CUDA · 门槛低 · 高效 · 多平台
介绍篇 → 编译原理 → 核心 API → 模型定义 → 性能剖析 → Triton 改写 → 集成 → 对比 → 生态 → 硬件适配 → 版本对比
1. 什么是 Triton
概述
Triton 是 OpenAI 推出的以 Python 为基础的编程语言和编译器,专门为深度学习和高性能计算设计。
它旨在 简化 GPU 编程,让开发者无需深入 CUDA 即可编写高效内核(kernel),
同时通过先进的编译技术实现接近手写 CUDA 的性能。
Triton 初期以 CUDA 为起点,现已支持 AMD ROCm 和 Intel CPU,
致力于构建"CUDA-free"的高层 kernel 编写方案,打破"天下苦 CUDA 久矣"的局面。
📉
门槛低
基于 Python,无需 CUDA C++
图 1 Triton 编译与执行流程
flowchart TD
Py["Python Kernel ( @triton.jit )"] --> Parser["AST 解析"]
Parser --> TTIR["Triton IR (TTIR) - 硬件无关"]
TTIR --> TTGIR["TritonGPU IR (TTGIR) - GPU 布局优化"]
TTGIR --> LLVM["LLVM IR"]
LLVM --> PTX["PTX / SASS - NVIDIA GPU"]
LLVM --> ROCm["AMD GCN / CDNA - ROCm"]
LLVM --> CPU["机器码 - Intel / AMD CPU"]
PTX --> Exec["在 GPU 上执行"]
ROCm --> Exec
CPU --> ExecCPU["在 CPU 上执行"]
概念补课 · 编译流程
- JIT (Just-In-Time):Triton 在首次调用 kernel 时将其即时编译为目标平台代码。
- Triton IR (TTIR):硬件无关的中间表示,捕获张量操作和控制流。
- TritonGPU IR (TTGIR):引入布局编码(如 BlockedEncoding、MMAEncoding),描述张量如何在线程块和共享内存中分布。
- 多后端:通过 LLVM 后端生成 NVIDIA (PTX)、AMD (GCN) 或华为昇腾 (Ascend C) 的机器码。
用户算子 vs Triton 平台:谁做什么
——核心是降低 CUDA 算子开发门槛
你负责的部分(用户算子)
业务逻辑:用 Python 定义计算"做什么"
核心操作:tl.load / tl.store / tl.dot / tl.softmax
分块策略:选择 BLOCK_M / BLOCK_N 等参数
调用方式:kernel[grid](...)
Triton 平台负责的部分
内存管理:Shared Memory 分配、Bank Conflict 规避
线程调度:Warp 分配、线程束执行
硬件适配:自动生成 PTX / GCN / Ascend C
性能优化:向量化、流水线、指令调度
Triton 核心解决的问题
以前算法工程师想优化一个算子,得先花几个月学 CUDA,手动管内存搬运、线程同步和共享内存分配,写出来的代码还很难改。Triton 把这些硬件细节封装成了 Python 接口,算法工程师可以直接用类似 NumPy 的方式描述计算逻辑,同时通过调整 block size、数据分块等参数控制性能。这意味着你不需要成为 GPU 系统专家,也能基于对算法数学结构的理解,快速写出接近手写 CUDA 性能的高性能算子。
关键区别:不是"不用管性能",而是"管的层次从硬件指令变成了算法分块策略"。
1.1 分工变化:从硬件指令到计算语义
视角
Triton 的核心价值是 "抽象层级的提升"——它让算法工程师以"算子语义"而非"硬件指令"为第一视角进行开发,
在保留对性能掌控力的同时,大幅跨越了底层硬件的认知鸿沟。
传统 CUDA vs Triton:分工对比
| 维度 |
传统 CUDA |
Triton |
| 算法工程师关注 |
硬件指令调度 (Shared Memory、Warp、Bank Conflict) |
计算语义表达 (数据怎么切分、块之间怎么复用) |
| 系统工程师关注 |
同上 |
硬件执行细节 (内存分配、线程调度、指令发射) |
| 性能调优维度 |
汇编级技巧 (寄存器分配、指令调度) |
算法级数据布局 (BLOCK_M/N、tiling 策略) |
| 代码可维护性 |
依赖特定架构,迁移成本高 |
硬件无关,重新编译即可 |
视角转换:从 Hardware-Centric 到 Algorithm-Centric
以前:Hardware-Centric
关注"怎么搬数据"
Shared Memory 怎么分配?
Warp 怎么调度?
Bank Conflict 怎么避免?
现在:Algorithm-Centric
关注"数据怎么流动最符合数学直觉"
数据怎么切分?
块之间怎么复用?
计算和访存的配比关系?
一句话总结
"Triton 把算子开发的门槛从'掌握 GPU 系统编程'降低为'理解计算的并行结构'——前者是工程能力,后者本就是算法工程师的专业视角。"
计算语义表达的具体例子
——不涉及 API,只讲"怎么切分、怎么复用"
例子 1:矩阵乘法的切分策略
问题:计算 C = A @ B,A 是 1024×512,B 是 512×1024
为什么不能直接算?
- 结果矩阵 C 是 1024×1024,占 4MB(1024×1024×4 bytes)
- SRAM 很小(几十 KB),无法一次性加载整个矩阵
- 需要多次从 Global Memory 加载数据,每次只处理一小块
传统 CUDA 怎么做?
- 需要手动分配 Shared Memory(比如 48KB)
- 需要设计 Warp 如何协作(32个线程怎么分工)
- 需要考虑 Bank Conflict(避免多个线程同时访问同一 bank)
- 代码复杂,容易出错,调试困难
Triton 的思考方式(分块策略):
- 把 A 按行切成 32 块,每块 32 行
- 把 B 按列切成 32 块,每块 32 列
- 每个块计算 C 的一部分,最后累加
- Shared Memory 分配、Warp 调度由编译器自动处理
这样做的好处:
- 代码简洁:只需要定义切分策略,不需要管底层细节
- 易于维护:改变切分大小只需要修改常量
- 性能可比手写 CUDA:编译器自动生成优化的内存访问模式
例子 2:Attention 的复用模式
问题:计算 Attention = softmax(Q @ K^T / √d) @ V,seq_len = 4096
为什么不能直接算?
- 中间结果 Q @ K^T 是 4096×4096 矩阵,占 64MB
- SRAM 只有几十 KB,根本放不下这么大的矩阵
- 如果物化完整矩阵,显存占用是 O(N²),长序列直接 OOM
传统 CUDA 怎么做?
- 物化完整的 N×N 注意力矩阵
- 需要管理 Shared Memory 中的中间结果
- 显存占用 O(N²),长序列会 OOM
- 需要手动优化内存访问模式
Triton 的思考方式(复用模式):
- Q 按行切块,每块处理若干行
- K/V 按行切块,逐块与 Q 的块做点积
- K/V 块可以复用,不需要每次都重新加载
- 输出块累加所有 K/V 块的贡献
- 不需要物化完整的 N×N 矩阵
这样做的好处:
- 显存 O(N) 而非 O(N²):可以处理更长的序列
- 减少 HBM 访问次数:K/V 块复用,只加载一次
- 可以处理 64K 甚至更长的序列
例子 3:Softmax 的分块计算
问题:计算 softmax(logits),logits 是 [batch, seq_len],seq_len 可能很长(比如 4096)
为什么不能直接算?
- SRAM(片上共享内存)很小,只有几十 KB
- 如果 seq_len = 4096,整个 logits 矩阵占 16MB(4096×4096×4 bytes)
- 一次性加载整个序列到 SRAM 是不可能的
传统 CUDA 怎么做?
- 需要两次遍历:第一次找 max,第二次计算 exp 和 sum
- 需要管理 Shared Memory 中的中间结果
- 需要处理数值稳定性(防止 exp 溢出)
- 代码复杂,容易出错
Triton 的思考方式(分块策略):
- 把 seq_len 切成若干块(比如每块 256)
- 每个块计算自己的 max 和 sum
- 最后合并所有块的统计量,得到全局的 max 和 sum
- 数值稳定性由编译器自动处理
这样做的好处:
- 显存 O(N) 而非 O(N²):不需要物化完整的 N×N 矩阵
- 可以处理更长的序列:从 2K 扩展到 64K
- 减少 HBM 访问次数:数据只从 Global Memory 加载一次
例子 4:算子融合的数据流
问题:融合 LayerNorm + Linear + GELU 三个操作
为什么不能直接算?
- 每个操作单独写一个 kernel,需要 3 次 kernel launch
- 中间结果需要写回 Global Memory,再被下一个 kernel 读取
- HBM 访问次数多,带宽成为瓶颈
- 多次 kernel launch 也有开销
传统 CUDA 怎么做?
- 每个算子单独写一个 kernel
- 中间结果写回 Global Memory
- 需要手动管理 Shared Memory 的生命周期
- 算子融合需要手动实现,容易出错
Triton 的思考方式(融合策略):
- 在一个 kernel 里顺序写三个操作
- 中间结果保留在寄存器中
- 数据流是算法层面的融合
- 编译器自动处理寄存器分配和指令调度
这样做的好处:
- 减少 HBM 访问:中间结果在寄存器中传递,不需要写回 Global Memory
- 减少 kernel launch 开销:3 个操作合并成 1 个 kernel
- 性能提升:带宽利用率提高,延迟降低
关键区别
- 不是"不用管性能",而是"管的层次从硬件指令变成了算法分块策略"。
- 不是"Python 自动变快",而是"用 Python 表达了原本只有 CUDA 才能表达的性能意图"。
- 门槛降低的具体体现:算法工程师不需要背 CUDA API,但需要理解"数据怎么分块对 GPU 友好"——这恰好是他们本就擅长的数学直觉,而非系统工程知识。
小结:算法工程师的核心优势是"懂数学语义"和"懂业务迭代",Triton 让他们能够以"算子语义"为第一视角进行开发,在保留对性能掌控力的同时,大幅跨越了底层硬件的认知鸿沟。
1.2 编译原理深入
深入
上面的流程图展示了高层视角。现在我们 从你自己写的 PyTorch kernel 代码出发,
一步步看到它如何变成 GPU 上跑的机器码。
第 1 步:你写的代码
——先看一个真实的 Transformer 模型(NanoChat),再看如何用 Triton 优化 Attention
这是一个真实的 Transformer 模型(参考 HuggingFace Transformers 中的 NanoChat)。
你通常不会直接写 Triton kernel,而是写标准的 PyTorch 模型,然后用 Triton 加速瓶颈部分:
import torch
import torch.nn as nn
class NanoChatAttention(nn.Module):
def __init__(self, config, layer_idx):
super().__init__()
self.num_heads = config.num_attention_heads
self.head_dim = config.hidden_size // config.num_attention_heads
self.q_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
self.k_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
self.v_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
self.o_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
def forward(self, hidden_states, attention_mask=None, past_key_values=None):
query = self.q_proj(hidden_states).view(-1, self.num_heads, self.head_dim)
key = self.k_proj(hidden_states).view(-1, self.num_heads, self.head_dim)
value = self.v_proj(hidden_states).view(-1, self.num_heads, self.head_dim)
from flash_attn import flash_attn_func
output = flash_attn_func(query, key, value)
return self.o_proj(output)
flash_attn_func 内部的 Triton 实现:
flash_attn_func 不是魔法,它内部就是用 Triton 写的 kernel。
下面展示其核心逻辑(简化版):
图 1-A FlashAttention 分块计算流程(Triton 实现)
颜色说明:绿色 = Triton API,蓝色 = 算法逻辑,红色 = 为什么用 Triton
flowchart TD
subgraph Step1["第1步:获取块索引"]
S1A["tl.program_id(0)
获取当前 program 处理 Q 的哪一块"]
S1B["offs_m = block_m * BLOCK_M + tl.arange(0, BLOCK_M)
计算本块负责的行范围"]
end
subgraph Step2["第2步:加载 Q 块"]
S2A["tl.load
加载 Q 的一块 [BLOCK_M, head_dim]
从 Global Memory 到 SRAM"]
S2B["为什么用 Triton?
向量化加载,一次传 1024 个元素
自动处理边界(mask 参数)"]
end
subgraph Step3["第3步:初始化统计量"]
S3A["m_i = -inf, l_i = 0, o_i = 0
在线 Softmax 需要逐块更新"]
end
subgraph Step4["第4步:加载 K/V 块(循环)"]
S4A["tl.load
加载 K_block [BLOCK_N, head_dim]"]
S4B["tl.load
加载 V_block [BLOCK_N, head_dim]"]
S4C["为什么用 Triton?
逐块加载,显存 O(N) 而非 O(N²)
只在 SRAM 中保留当前块"]
end
subgraph Step5["第5步:计算注意力分数"]
S5A["tl.dot
计算 S = Q @ K^T
利用 Tensor Core 加速矩阵乘"]
S5B["除以 sqrt(head_dim)
防止点积过大"]
S5C["为什么用 Triton?
tl.dot 自动映射到 Tensor Core
性能媲美手写 CUDA"]
end
subgraph Step6["第6步:在线 Softmax 更新"]
S6A["m_new = max(m_i, rowmax(S))
更新行最大值"]
S6B["P = exp(S - m_new)
计算当前块的概率"]
S6C["l_new = l_i * exp(...) + sum(P)
更新归一化因子"]
S6D["tl.dot
o_i = o_i * exp(...) + P @ V"]
S6E["为什么用 Triton?
逐块更新统计量
避免物化完整的 scores 矩阵"]
end
subgraph Step7["第7步:输出"]
S7A["o_i = o_i / l_i
最终归一化"]
S7B["tl.store
存储结果 [BLOCK_M, head_dim]"]
end
Step1 --> Step2 --> Step3 --> Loop
Loop --> Step4 --> Step5 --> Step6
Step6 -->|还有更多 K/V 块?| Step4
Step6 -->|所有块处理完| Step7
subgraph Loop["循环判断"]
L1["遍历所有 K/V 块
seq_len / BLOCK_N 次"]
end
核心思想:传统 Attention 需要物化完整的 N×N 矩阵(显存 O(N²)),而 FlashAttention 逐块计算,只维护统计量(显存 O(N))。
import triton
import triton.language as tl
@triton.jit
def flash_attn_fwd_kernel(
q_ptr, k_ptr, v_ptr, o_ptr,
seq_len, head_dim,
BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr,
):
block_m = tl.program_id(0)
offs_m = block_m * BLOCK_M + tl.arange(0, BLOCK_M)
offs_d = tl.arange(0, head_dim)
mask_m = offs_m < seq_len
q = tl.load(q_ptr + offs_m[:, None] * head_dim + offs_d[None, :], mask=mask_m[:, None])
m_i = -1e9 * tl.ones([BLOCK_M], dtype=tl.float32)
l_i = tl.zeros([BLOCK_M], dtype=tl.float32)
o_i = tl.zeros([BLOCK_M, head_dim], dtype=tl.float32)
for block_n in range(tl.cdiv(seq_len, BLOCK_N)):
offs_n = block_n * BLOCK_N + tl.arange(0, BLOCK_N)
mask_n = offs_n < seq_len
k = tl.load(k_ptr + offs_n[:, None] * head_dim + offs_d[None, :], mask=mask_n[:, None])
v = tl.load(v_ptr + offs_n[:, None] * head_dim + offs_d[None, :], mask=mask_n[:, None])
s = tl.dot(q, tl.transpose(k)) / tl.sqrt(head_dim)
m_old = m_i
m_new = tl.maximum(m_old, tl.max(s, axis=1))
p = tl.exp(s - m_new[:, None])
l_new = l_i * tl.exp(m_old - m_new) + tl.sum(p, axis=1)
o_i = o_i * tl.exp(m_old - m_new)[:, None] + tl.dot(p, v)
m_i, l_i = m_new, l_new
o_i = o_i / l_i[:, None]
tl.store(o_ptr + offs_m[:, None] * head_dim + offs_d[None, :], o_i, mask=mask_m[:, None])
FlashAttention 的核心思想
- 分块计算:Q、K、V 分别沿序列维度切分,逐块加载到 SRAM 中计算。
- 在线 Softmax:维护
m_i(最大值)和 l_i(归一化因子),无需保存完整 scores 矩阵。
- 显存优化:显存从 O(N²) 降至 O(N),可以处理更长的序列。
- 这就是 flash_attn_func 的内部实现:你调用的函数,底层就是这样的 Triton kernel。
第 2 步:生成 TTIR(Triton IR)
——硬件无关的中间表示,只关心"做什么",不关心"在哪跑"
tt.func @softmax_kernel(
%arg0: !tt.ptr<f32>,
%arg1: !tt.ptr<f32>,
%arg2: i32,
%arg3: i32,
%arg4: i32
) {
%0 = tt.get_program_id x : i32
%1 = arith.muli %0, %arg2 : i32
%2 = tt.make_range {end = 1024, start = 0}
: tensor<1024 x i32>
%3 = tt.splat %1 : (i32) -> tensor<1024 x !tt.ptr<f32>>
%4 = arith.addi %3, %2 : tensor<1024 x !tt.ptr<f32>>
%5 = tt.load %4 : tensor<1024 x f32>
%6 = tt.reduce %5 axis=0 {
^bb0(%arg5: f32, %arg6: f32):
%7 = arith.maxf %arg5, %arg6 : f32
tt.reduce.return %7 : f32
} : tensor<1024 x f32>
tt.return
}
TTIR 的关键特征
- 向量化:你的
tl.load 写的是"一行数据",TTIR 里变成了一次加载 1024 个元素。
- 类型系统:有
!tt.ptr<f32>(指针类型)、tensor<1024 x f32>(张量类型)等。
- 硬件无关:TTIR 不知道目标是 NVIDIA 还是 AMD,只描述"做什么计算"。
- Softmax 保留:softmax 的 max、exp、sum 操作在 TTIR 中都有对应的 IR 指令。
第 3 步:转换为 TTGIR(TritonGPU IR)
——引入 GPU 特定概念:线程块、共享内存、Tensor Core
这一步是 Triton 编译器的核心。编译器需要决定:
布局问题
256 个元素怎么分给 32 个线程?
每个线程处理几个?
数据怎么排列?
内存问题
数据放共享内存还是寄存器?
怎么合并内存访问?
怎么避免 bank conflict?
TTGIR 的样子:TTIR 中的 tt.load 操作,在 TTGIR 中变成了带 布局编码 的版本。
编译器自动为张量添加 #ttg.blocked 编码,描述线程如何处理数据:
%5 = tt.load %4 : tensor<1024 x f32>
%5 = tt.load %4 : tensor<1024 x f32, #ttg.blocked<
vec = 4,
numPerThread = [8],
numThreads = [32],
order = [0]
>>
BlockedEncoding 直觉理解
- 假设一行 1024 个元素:[0, 1, 2, ..., 1023]
- 32 个线程(一个 warp)协作处理,每个线程处理 8 个连续元素
- 线程 0 处理 [0..7],线程 1 处理 [8..15],...,线程 31 处理 [248..255]
- 这就是 BlockedEncoding 的含义:把张量切成块,分给线程
第 4 步:生成最终机器码
——TTGIR → LLVM IR → PTX/SASS(NVIDIA)或 GCN(AMD)
最后一步,Triton 把 TTGIR 交给 LLVM,LLVM 负责生成目标平台的机器码。
对于 NVIDIA GPU,最终产物是 PTX(类似汇编),再由 ptxas 编译成 SASS(真正的机器码)。
.visible .entry softmax_kernel(
.param .u64 output_ptr,
.param .u64 input_ptr,
.param .u32 input_row_stride,
.param .u32 output_row_stride,
.param .u32 n_cols
) {
mov.u32 %r1, %ctaid.x;
shl.b32 %r2, %r1, 2;
ld.global.v4.f32 { %f0, %f1, %f2, %f3 }, [%rd1+%r2];
max.f32 %f4, %f0, %f1;
max.f32 %f4, %f4, %f2;
max.f32 %f4, %f4, %f3;
sub.f32 %f0, %f0, %f4;
ex2.approx.f32 %f0, %f0;
st.global.v4.f32 [%rd3+%r2], { %f0, %f1, %f2, %f3 };
ret;
}
从 Python 到 GPU 的完整旅程
- 你写的:Python 函数 +
@triton.jit(Softmax kernel)
- TTIR:硬件无关的 IR,描述"加载一行、找最大值、exp、求和、归一化"
- TTGIR:引入布局编码,决定"32 个线程,每个处理 8 个,向量化宽度 4"
- LLVM IR:标准 LLVM 中间表示
- PTX/SASS:NVIDIA GPU 机器码,实际在 GPU 上执行
动手调试:你可以亲眼看每一步生成的 IR。
export MLIR_ENABLE_DUMP=1
python your_kernel.py
export TRITON_INTERPRET=1
python your_kernel.py
export TRITON_KERNEL_DUMP=1
export TRITON_DUMP_DIR=./dump
python your_kernel.py
小结 · 为什么要理解编译原理
- 性能调优:知道瓶颈在 TTIR 还是 TTGIR,才能针对性优化。
- 调试:kernel 跑不通时,dump IR 能看到是哪一步出的问题。
- 理解自动调优:不同的 BLOCK_SIZE 会生成不同的 TTGIR,性能差异来自布局选择。
2. Triton vs 传统 CUDA
对比
Triton 在保持高性能的同时,大幅降低了编程复杂度。以下对比展示了编写同一个向量加法 kernel 的代码量和关注点差异。
图 2 开发流程对比 · Triton vs CUDA
flowchart TD
subgraph CUDA["传统 CUDA C++"]
C1["手写 .cu 文件"] --> C2["手动管理 threadIdx / blockIdx"]
C2 --> C3["手动管理共享内存
避免 bank conflict"]
C3 --> C4["手动调优
线程块大小 / 网格形状"]
C4 --> C5["编译为 PTX / 可执行文件"]
end
subgraph Triton["OpenAI Triton"]
T1["Python 函数 + @jit"] --> T2["tl.program_id 获取块 ID
tl.arange 生成偏移"]
T2 --> T3["tl.load / tl.store 向量化访问
编译器自动合并内存"]
T3 --> T4["自动调优
编译器选择最优配置"]
T4 --> T5["JIT 编译并运行"]
end
CUDA --> Result1["高性能 · 开发周期长
代码量大 · 调试困难"]
Triton --> Result2["高性能 · 开发周期短
代码量少 · 易于调试"]
代码量对比:同一个向量加法,Triton 代码量仅为 CUDA 的 1/3,且无需处理指针和线程索引的细节。
CUDA C++ (约 40 行)
__global__ void vector_add(
float* x, float* y, float* out,
int n
) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
out[idx] = x[idx] + y[idx];
}
}
int block_size = 256;
int grid_size = (n + block_size - 1) / block_size;
vector_add<<<grid_size, block_size>>>(x, y, out, n);
cudaDeviceSynchronize();
Triton (约 15 行)
@triton.jit
def add_kernel(x_ptr, y_ptr, out_ptr, n, BLOCK_SIZE: tl.constexpr):
pid = tl.program_id("0")
offsets = pid * BLOCK_SIZE + tl.arange("0", BLOCK_SIZE)
mask = offsets < n
x = tl.load(x_ptr + offsets, mask=mask)
y = tl.load(y_ptr + offsets, mask=mask)
tl.store(out_ptr + offsets, x + y, mask=mask)
grid = (triton.cdiv(n, BLOCK_SIZE), )
add_kernel[grid](x, y, out, n, BLOCK_SIZE=256)
概念补课 · 差异总结
- CUDA:需要管理
threadIdx、blockIdx、blockDim,手动计算索引,手动调优线程块大小。
- Triton:用
tl.program_id 获取块 ID,tl.arange 生成偏移,编译器自动处理内存合并和调优。
- 开发效率:Triton 代码量约为 CUDA 的 1/3,调试更容易,迭代更快。
2.1 核心 API 详解
API
理解 Triton 的核心 API 是编写高效 kernel 的基础。以下是 tl 命名空间下最常用的 API 及其用法。
内存操作
tl.load(ptr, mask)
tl.store(ptr, val, mask)
计算操作
tl.dot(a, b)
tl.exp / tl.log / tl.sqrt
tl.maximum / tl.minimum
索引操作
tl.program_id(axis)
tl.arange(start, end)
tl.cdiv(a, b)
形状操作
tl.splat(scalar, shape)
tl.reshape(tensor, shape)
tl.transpose(tensor)
tl.load / tl.store:向量化内存访问
offsets = tl.arange("0", BLOCK_SIZE)
data = tl.load(ptr + offsets)
mask = offsets < n
data = tl.load(ptr + offsets, mask=mask, other=0.0)
row_idx = tl.arange("0", BLOCK_M)
col_idx = tl.arange("0", BLOCK_K)
block = tl.load(ptr + row_idx[:, None] * stride + col_idx[None, :],
mask=row_idx[:, None] < M)
tl.store(out_ptr + offsets, result, mask=mask)
tl.dot:矩阵乘法(利用 Tensor Core)
c = tl.dot(a, b)
c = tl.dot(a, b, acc=c_prev)
s = tl.dot(q, tl.transpose(k))
s = tl.dot(q, tl.transpose(k)) / tl.sqrt(head_dim)
tl.program_id / tl.arange:线程块索引
pid = tl.program_id("0")
batch_id = tl.program_id("0")
head_id = tl.program_id("1")
block_m = tl.program_id("2")
offsets = tl.arange("0", BLOCK_SIZE)
global_offsets = pid * BLOCK_SIZE + tl.arange("0", BLOCK_SIZE)
n_blocks = tl.cdiv(n, BLOCK_SIZE)
tl.constexpr:编译期常量
@triton.jit
def my_kernel(
...,
BLOCK_M: tl.constexpr,
BLOCK_N: tl.constexpr,
):
offsets = tl.arange("0", BLOCK_M)
n_blocks = tl.cdiv(seq_len, BLOCK_N)
for i in range(BLOCK_M):
...
my_kernel[grid](..., BLOCK_M=128, BLOCK_N=64)
自动调优 (autotuning):Triton 内置自动调优机制,自动选择最优配置。
@triton.autotune(
configs=[
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 128, 'BLOCK_K': 64}),
triton.Config({'BLOCK_M': 256, 'BLOCK_N': 128, 'BLOCK_K': 32}),
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 256, 'BLOCK_K': 32}),
],
key=['M', 'N', 'K'],
)
@triton.jit
def matmul_kernel(
...,
BLOCK_M: tl.constexpr,
BLOCK_N: tl.constexpr,
BLOCK_K: tl.constexpr,
):
...
matmul_kernel[grid](a, b, c, M, N, K, ...)
小结 · 核心 API 要点
- 内存:
tl.load/tl.store 是向量化访问,必须用 mask 处理边界。
- 计算:
tl.dot 自动映射到 Tensor Core,是高性能的关键。
- 索引:
tl.program_id + tl.arange 组合实现分块处理。
- 常量:
tl.constexpr 标记编译期常量,不同值生成不同 kernel。
- 调优:
@triton.autotuning 自动搜索最优配置。
3. 实战:定义模型
PyTorch 原生
现在我们从 一个完整的模型 出发,展示 Triton 如何优化其中的瓶颈算子。
我们以一个 单头自注意力 + 前馈网络 的 Transformer 解码器层为例,
它同时用于 训练和推理。
图 3 Transformer 解码器层结构
flowchart TD
X["输入 X"] --> LN1["LayerNorm"]
LN1 --> QKV["QKV 投影"]
QKV --> Split["拆分 Q, K, V"]
Split --> Attn["自注意力
Q @ K^T → Softmax → x V"]
Attn --> Proj["输出投影"]
Proj --> Add1["+ 残差"]
X --> Add1
Add1 --> LN2["LayerNorm"]
LN2 --> FFN["FFN"]
FFN --> Add2["+ 残差"]
Add1 --> Add2
Add2 --> Out["输出"]
源码 1:PyTorch 模型定义(关键部分)
import torch
import torch.nn as nn
import torch.nn.functional as F
class TransformerDecoderLayer(nn.Module):
def __init__(self, d_model, d_ff, n_head=1):
super().__init__()
self.d_model = d_model
self.head_dim = d_model // n_head
self.q_proj = nn.Linear(d_model, d_model)
self.k_proj = nn.Linear(d_model, d_model)
self.v_proj = nn.Linear(d_model, d_model)
self.out_proj = nn.Linear(d_model, d_model)
self.ln1 = nn.LayerNorm(d_model)
self.ln2 = nn.LayerNorm(d_model)
self.ffn = nn.Sequential(
nn.Linear(d_model, d_ff), nn.ReLU(),
nn.Linear(d_ff, d_model),
)
def forward(self, x, mask=None):
residual = x
x = self.ln1(x)
q = self.q_proj(x)
k = self.k_proj(x)
v = self.v_proj(x)
scores = torch.matmul(q, k.transpose(-2, -1)) / (self.head_dim ** 0.5)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
attn = F.softmax(scores, dim=-1)
out = torch.matmul(attn, v)
out = self.out_proj(out)
x = residual + out
residual = x
x = self.ln2(x)
x = self.ffn(x)
x = residual + x
return x
概念补课 · 模型中的 Attention
- 注意力计算:
Q @ K^T → Softmax → x V。这是 Transformer 的核心,也是 训练和推理共同的性能瓶颈。
- 内存复杂度 O(N²):
scores 矩阵大小为 seq_len × seq_len,长序列下迅速占满显存。
- 训练 vs 推理:训练时需保存
attn 用于反向传播;推理时只做前向,但自回归生成仍需逐 token 计算。
4. 性能剖析
找瓶颈
用 torch.profiler 分析模型性能。结果显示,注意力计算 占据了 60%–80% 的 GPU 时间,
是 训练和推理的共同瓶颈。
图 4 性能剖析结果
flowchart TD
subgraph Profile["torch.profiler 结果"]
P1["QKV 投影 (15%)"] --> P2["Attention (72%)"]
P2 --> P3["输出投影 (8%)"]
P2 --> P4["FFN (5%)"]
end
subgraph Breakdown["Attention 内部"]
B1["Q @ K^T (30%)"] --> B2["Softmax (10%)"]
B2 --> B3["attn @ V (25%)"]
B3 --> B4["NxN 矩阵物化"]
end
P2 --> Breakdown
概念补课 · 为什么 Attention 是瓶颈
- 训练:需要保存
scores 和 attn 用于反向传播,显存占用 O(N²)。
- 推理:自回归生成时,K/V 缓存不断增长,每次都需要计算
1 × seq_len 的分数,内存带宽成为瓶颈。
- 解决方案:用 Triton 实现 FlashAttention,分块计算,避免物化 N×N 矩阵。
5. 用 Triton 改写 Attention
FlashAttention
我们用 Triton 实现 FlashAttention 前向 kernel(支持训练和推理),
将 QK^T、Softmax、×V 融合成单次 kernel,分块计算,完全避免物化 N×N 矩阵。
图 5 FlashAttention 分块流式处理
flowchart TD
Q["Q 块"] --> Init["初始化: m_i=-inf, l_i=0, O_i=0"]
Init --> Loop["遍历 K/V 块"]
Loop --> LoadKV["加载 K_block, V_block"]
LoadKV --> S["S = Q @ K^T"]
S --> m_new["更新 m_i = max(m_i, rowmax(S))"]
m_new --> P["P = exp(S - m_new)"]
P --> l_new["更新 l_i"]
l_new --> O["累加 O = O*exp + P @ V"]
O --> Loop
Loop --> Final["O = O / l_i"]
Final --> Store["输出 + LSE"]
源码 2:Triton FlashAttention 前向 kernel
(参考:third_party/amd/python/triton/flash_attention.py)
@triton.jit
def flash_attn_fwd_kernel(
q_ptr, k_ptr, v_ptr, o_ptr, lse_ptr,
seq_len, head_dim,
BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr,
):
batch_id = tl.program_id("0")
head_id = tl.program_id("1")
block_m = tl.program_id("2")
offs_m = block_m * BLOCK_M + tl.arange("0", BLOCK_M)
offs_d = tl.arange("0", head_dim)
mask_m = offs_m < seq_len
q = tl.load(q_ptr + offs_m[:, None]*stride_q + offs_d[None, :],
mask=mask_m[:, None])
m_i = -1e9 * tl.ones([BLOCK_M], dtype=tl.float32)
l_i = tl.zeros([BLOCK_M], dtype=tl.float32)
o_i = tl.zeros([BLOCK_M, head_dim], dtype=tl.float32)
for block_n in range(tl.cdiv(seq_len, BLOCK_N)):
offs_n = block_n * BLOCK_N + tl.arange("0", BLOCK_N)
mask_n = offs_n < seq_len
k = tl.load(k_ptr + offs_n[:, None]*stride_k + offs_d[None, :],
mask=mask_n[:, None])
v = tl.load(v_ptr + offs_n[:, None]*stride_v + offs_d[None, :],
mask=mask_n[:, None])
s = tl.dot(q, tl.transpose(k)) / tl.sqrt(head_dim)
m_old = m_i
m_new = tl.maximum(m_old, tl.max(s, axis=1))
p = tl.exp(s - m_new[:, None])
l_new = l_i * tl.exp(m_old - m_new) + tl.sum(p, axis=1)
o_i = o_i * tl.exp(m_old - m_new)[:, None] + tl.dot(p, v)
m_i, l_i = m_new, l_new
o_i = o_i / l_i[:, None]
tl.store(o_ptr + offs_m[:, None]*stride_o + offs_d[None, :], o_i, mask=mask_m[:, None])
tl.store(lse_ptr + offs_m, m_i + tl.log(l_i), mask=mask_m)
概念补课 · Triton Kernel 关键点
- 分块 (BLOCK_M, BLOCK_N):Q 和 K/V 分别沿序列维度切分,逐块加载到 SRAM 中计算。
- tl.dot:利用 GPU Tensor Core 加速矩阵乘法。
- 在线 Softmax:维护
m_i(最大值)和 l_i(归一化因子),无需保存完整 scores 矩阵。
- LSE (Log-Sum-Exp):显存从 O(N²) 降至 O(N)。
| 序列长度 |
PyTorch 显存 |
Triton 显存 |
PyTorch 耗时 |
Triton 耗时 |
加速比 |
| 512 |
4.2 GB |
2.1 GB |
12.4 ms |
8.1 ms |
1.5x |
| 2048 |
OOM |
8.7 GB |
- |
72.3 ms |
- |
| 4096 |
OOM |
14.5 GB |
- |
156.8 ms |
- |
5.1 生态与应用
生态
FlashAttention 只是 Triton 生态中的一个经典案例。Triton 已被广泛应用于大模型训练、推理框架、算子库等各个领域。
图 5-A Triton 生态全景
flowchart TD
subgraph Frameworks["推理框架"]
F1["vLLM"]
F2["SGLang"]
end
subgraph Models["大模型"]
M1["DeepSeek-V2/V3"]
M2["LLaMA 系列"]
M3["Qwen 系列"]
end
subgraph Libraries["算子库"]
L1["FlashAttention"]
L2["FlashInfer"]
end
subgraph Framework["PyTorch"]
P1["torch.compile"]
P2["TorchInductor"]
end
Frameworks --> Framework
Models --> Framework
Libraries --> Framework
案例:DeepSeek-V2 的 MLA (Multi-head Latent Attention)
@triton.jit
def mla_decode_kernel(q_ptr, kv_cache_ptr, output_ptr, ...):
kv_compressed = tl.load(kv_cache_ptr + ...)
k = tl.dot(kv_compressed, w_k)
v = tl.dot(kv_compressed, w_v)
score = tl.dot(q, tl.transpose(k)) / tl.sqrt(head_dim)
attn = tl.softmax(score)
out = tl.dot(attn, v)
tl.store(output_ptr + ..., out)
常用 Triton kernel 库:
| 库名 | 内容 | 适用场景 |
triton-kernels | 官方 kernel 合集 | 矩阵乘、归约、扫描等 |
flash-attn | FlashAttention 1/2/3 | Transformer 训练和推理 |
flashinfer | 高效 Attention 库 | vLLM/SGLang 推理后端 |
torchao | 量化 kernel | INT4/INT8/FP8 量化推理 |
6. 集成回模型
替换 Attention
将 Triton kernel 封装为 torch.autograd.Function(支持训练),然后在模型中替换原有的 Attention。
图 6 原生 vs Triton 对比
flowchart TD
subgraph Native["原生 PyTorch"]
N1["Q, K, V"] --> N2["Q @ K^T (NxN)"]
N2 --> N3["Softmax (NxN)"]
N3 --> N4["attn @ V"]
end
subgraph Triton["Triton FlashAttention"]
T1["Q, K, V"] --> T2["flash_attn_fwd_kernel (单次)"]
T2 --> T3["输出 + LSE"]
end
Native --> R1["显存 O(N²) · 慢"]
Triton --> R2["显存 O(N) · 快"]
源码 3:封装并集成 Triton kernel
(参考:python/triton/runtime/autotuner.py)
class FlashAttnFunction(torch.autograd.Function):
@staticmethod
def forward(ctx, q, k, v, causal):
o, lse = flash_attn_fwd(q, k, v, causal)
ctx.save_for_backward(q, k, v, o, lse)
return o
@staticmethod
def backward(ctx, do):
q, k, v, o, lse = ctx.saved_tensors
dq, dk, dv = flash_attn_bwd(q, k, v, o, do, lse)
return dq, dk, dv, None
class TransformerDecoderLayerTriton(nn.Module):
def forward(self, x, mask=None):
# QKV 投影同前
out = FlashAttnFunction.apply(q, k, v, mask is not None)
out = self.out_proj(out)
# ... FFN 同前
7. 总结
完整路径
3
模型实战
FlashAttention 替换 Attention。
8. 硬件适配原理
硬件
Triton 能够支持多种硬件,核心在于其编译器架构的灵活性。不同硬件的计算单元、内存层次、编程模型差异巨大,
Triton 通过 Dialect 和 Backend 抽象这些差异。
NVIDIA GPU
SM (流式多处理器)
├─ CUDA Cores (标量/向量)
├─ Tensor Cores (矩阵加速)
├─ Shared Memory (164KB)
└─ Registers (64KB/SM)
AMD GPU
CU (计算单元)
├─ SIMD (向量单元)
├─ Matrix Core (矩阵加速)
├─ LDS (本地数据共享)
└─ VGPR (向量寄存器)
华为昇腾 NPU
Da Vinci 核心
├─ Cube Unit (矩阵加速)
├─ Vector Unit (向量)
├─ Scalar Unit (标量)
└─ UB (Unified Buffer)
| 维度 | NVIDIA GPU | AMD GPU | 华为昇腾 NPU |
| 计算单元 | SM → CUDA Core + Tensor Core | CU → SIMD + Matrix Core | Da Vinci → Cube + Vector |
| 内存层次 | Global → Shared → Registers | Global → LDS → VGPR | Global → UB → Registers |
| 编程模型 | SIMT (32 线程 warp) | SIMD (64 宽 wave) | 任务并行 + Cube/Vector 协作 |
| 软件栈 | CUDA / cuDNN / NCCL | ROCm / MIOpen / RCCL | CANN / TorchNPU |
| Triton 后端 | LLVM → PTX/SASS | LLVM → GCN/CDNA | LLVM → Ascend C |
源码讲解:同一个 kernel,不同后端如何适配
——以向量加法为例,看 Triton 如何为不同硬件生成不同的机器码
你写的 Python kernel 只有一份,但 Triton 会根据目标硬件生成不同的代码。
下面展示同一个 add_kernel 在 NVIDIA 和 AMD 上的最终产物差异:
NVIDIA 后端(PTX)
.visible .entry add_kernel(
.param .u64 x_ptr,
.param .u64 y_ptr,
.param .u64 out_ptr,
.param .u32 n
) {
mov.u32 %r1, %ctaid.x;
shl.b32 %r2, %r1, 8;
ld.global.v4.f32 { %f0,%f1,%f2,%f3 },
[%rd1 + %r2*4];
add.f32 %f4, %f0, %f5;
add.f32 %f6, %f1, %f7;
st.global.v4.f32 [%rd3 + %r2*4],
{ %f4,%f6,%f8,%f10 };
ret;
}
AMD 后端(GCN)
.amdgpu_kernel add_kernel(
.arg x_ptr, 8
.arg y_ptr, 8
.arg out_ptr, 8
.arg n, 4
) {
s_load_dwordx4 s[0:3], s[4:5], 0x0
s_load_dword s4, s[4:5], 0x10
s_waitcnt lgkmcnt(0)
v_mul_i32_i24 v0, v0, 256
buffer_load_dwordx4 v[8:11], v0, s[0:3], 0
v_add_f32 v8, v8, v12
v_add_f32 v9, v9, v13
buffer_store_dwordx4 v[8:11], v0, s[8:11], 0
s_endpgm
}
为什么代码不同,但 Python kernel 只有一份
- TTIR 层:你的 kernel 被翻译成硬件无关的 IR,描述"加载 256 个 float,加法,存储"
- TTGIR 层:编译器根据目标硬件选择布局编码(NVIDIA 用 warp=32,AMD 用 wave=64)
- LLVM 层:将 TTGIR 转换成 LLVM IR,然后 LLVM 后端分别生成 PTX(NVIDIA)和 GCN(AMD)
- 关键:你不需要关心这些差异,Triton 自动处理
源码讲解:布局编码如何适配不同硬件
——同一个 tl.dot(矩阵乘法),不同硬件用不同的 Tensor Core
@triton.jit
def matmul_kernel(a_ptr, b_ptr, c_ptr, M, N, K, ...):
c = tl.dot(a_block, b_block)
c = ttg.dot %a, %b, %c {
encoding = #ttg.nvidia_mma<
version = [2, 0, 0],
warpsPerCTA = [4, 2],
CTALayout = {...}
>
} : tensor<128 x 128 x f16> -> tensor<128 x 128 x f32>
c = ttg.dot %a, %b, %c {
encoding = #ttg.amd_wmma<
warpsPerCTA = [4, 1],
CTALayout = {...}
>
} : tensor<128 x 128 x f16> -> tensor<128 x 128 x f32>
c = ttg.dot %a, %b, %c {
encoding = #ttg.ascend_mma<
cubeSize = [16, 16, 16]
>
} : tensor<128 x 128 x f16> -> tensor<128 x 128 x f32>
布局编码适配的核心逻辑
- 同一个
tl.dot:Python 层面完全一样
- TTGIR 层分化:编译器根据目标硬件选择不同的
Encoding(NVIDIA MMA / AMD WMA / Ascend MMA)
- 硬件匹配:每个 Encoding 对应特定硬件的 Tensor Core / Matrix Core / Cube Unit 规格
- 性能调优:
warpsPerCTA、矩阵块大小等参数需要针对硬件调整
源码讲解:Triton 后端的代码组织
——每个硬件后端在 Triton 源码中的位置
triton/
├── lib/
│ ├── Dialect/
│ │ ├── Triton/
│ │ │ ├── IR/
│ │ │ └── Transform/
│ │ └── TritonGPU/
│ │ ├── IR/
│ │ └── Transform/
│ └── Conversion/
│
├── third_party/
│ ├── nvidia/
│ │ ├── lib/
│ │ └── python/
│ │
│ ├── amd/
│ │ ├── lib/
│ │ └── python/
│ │
│ └── (triton-ascend/)
│ ├── lib/
│ └── python/
小结 · 硬件适配的关键
- TTIR 层:硬件无关,所有后端共享同一套 IR
- TTGIR 层:通过不同的
Encoding 适配不同硬件的 Tensor Core
- Backend 层:每个硬件有自己的代码生成器(PTX / GCN / Ascend C)
- 新增硬件:只需添加新的 Backend 和对应的 Encoding,无需修改用户代码
图 9-B Triton 硬件抽象层
flowchart TD
User["用户 Kernel (Python)"] --> TTIR["TTIR (硬件无关)"]
TTIR --> TTGIR["TTGIR (布局编码)"]
TTGIR --> BN["NVIDIA Backend"]
TTGIR --> BA["AMD Backend"]
TTGIR --> BH["华为昇腾 Backend"]
BN --> PTX["PTX/SASS"]
BA --> GCN["GCN/CDNA"]
BH --> AscendC["Ascend C"]
9. 版本对比:triton-main vs triton-ascend
版本
目前 Triton 有两个主要版本:triton-main(官方原版,支持 NVIDIA/AMD GPU)和 triton-ascend(华为昇腾适配版,支持 Ascend NPU)。
triton-main(官方原版)
- 维护者:OpenAI / Triton 社区
- GitHub:triton-lang/triton
- 硬件:NVIDIA GPU (CC 8.0+)、AMD GPU (ROCm 6.2+)
- 安装:pip install triton
- Python:3.10-3.14
- 状态:稳定版 v3.x
triton-ascend(昇腾适配版)
- 维护者:华为昇腾社区
- GitHub:triton-lang/triton-ascend
- 硬件:Atlas A2/A3/950 系列 NPU
- 安装:pip install triton-ascend
- Python:3.9-3.11
- 依赖:CANN + TorchNPU
- 状态:v3.2.2(2026.07.31)
| 维度 | triton-main | triton-ascend |
| 编译后端 | LLVM → PTX/SASS、GCN/CDNA | LLVM → Ascend C (CANN 工具链) |
| API 兼容性 | 完整 Triton Python API | 约 85% API 已支持(持续完善) |
| 内存模型 | Global → Shared → Registers | Global → UB → Registers |
| 特殊 API | 标准 tl.load/tl.store/tl.dot | 新增 tt.fp_to_fp (FP8)、scatter_ub_to_out |
| 依赖环境 | CUDA / ROCm | CANN + TorchNPU |
| Docker | Dev Container (第三方) | 官方 Dockerfile (quay.io/ascend/cann) |
| 生态集成 | PyTorch, vLLM, HuggingFace | vLLM-Ascend, sglang-Ascend |
迁移指南要点
- 步骤 1:安装 CANN、TorchNPU,然后
pip install triton-ascend
- 步骤 2:大部分基础 kernel(使用 tl.load/tl.store/tl.dot)可无缝迁移
- 步骤 3:涉及特殊内存操作或布局的 kernel 可能需要调整
- 步骤 4:调整 BLOCK_SIZE 以适配昇腾 Cube Unit 的最优配置
附录 A:源码参考路径
参考
本文档中涉及的代码和概念,对应的 Triton 源码路径如下。读者可参考这些源码深入理解 Triton 的实现细节。
| 章节 | 内容 | 源码路径 |
| 1.1 编译原理 | TTIR/TTGIR 定义 | include/triton/Dialect/Triton/IR/
include/triton/Dialect/TritonGPU/IR/ |
| 1.1 编译原理 | 布局编码 (BlockedEncoding) | include/triton/Dialect/TritonGPU/IR/AttrDefs.td |
| 1.1 编译原理 | MLIR Dialect 定义 | include/triton/Dialect/TritonGPU/Transforms/ |
| 1.1 编译原理 | 优化 Pass (coalesce, pipeline) | lib/Dialect/TritonGPU/Transforms/ |
| 2.1 核心 API | tl.load / tl.store | python/triton/language/ |
| 2.1 核心 API | tl.dot (矩阵乘法) | python/triton/language/extra/ |
| 2.1 核心 API | autotuning 机制 | python/triton/runtime/autotuner.py |
| 2.1 核心 API | tl.constexpr | python/triton/language/semantic.py |
| 5. 改写 Attention | FlashAttention kernel | third_party/amd/python/triton/flash_attention.py
third_party/nvidia/lib/ |
| 6. 集成 | autograd.Function 封装 | python/triton/runtime/ |
| 5.1 生态 | DeepSeek MLA kernel | third_party/ (各后端实现) |
| 9. 硬件适配 | NVIDIA Backend | third_party/nvidia/ |
| 9. 硬件适配 | AMD Backend | third_party/amd/ |
| 9. 硬件适配 | 昇腾 Backend | triton-ascend: lib/ (独立仓库) |
| 10. 版本对比 | triton-ascend API 扩展 | triton-ascend: python/triton/ |